package practise.hibernate.utils;

import org.apache.log4j.Logger;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;

public class EntityRelationshipHandler {
    public static String buildLogMessage(final String message, final Object... replacementParameters) {
        return message;
    }

    private static String[] collectionSuffix = new String[]{"s", "List", "Set"};
    private static Class[] collectionType = new Class[]{Set.class, List.class};
    private static final Logger LOGGER = Logger.getLogger(EntityRelationshipHandler.class);
    private static String packageName = "practise.hibernate";

    /**
     * Use to delete a Entity, before delete, it will release all the relationships for this entity,
     * this method will make sure make the operation at bidirection, for reference type ManyTOMany and OneToMany.
     *
     * @param entityManager entityManager from context
     * @param entity        the entity we want to delete
     */
    @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
    public static void deleteEntity(EntityManager entityManager, Object entity) {
        LOGGER.debug(buildLogMessage("Delete a entity:{0}", entity));
        //all the relationship released at first
        removeAllForeignRelationship(entity);
        //then remove the entity
        entityManager.remove(entity);
        //and make a flush
        entityManager.flush();
    }

    /**
     * Use to update te Entity relationship, it will remove the relationship with the specified oldForeignEntity at first,
     * then create the relationship with the newForeignEntity, this method will make sure make the operation in bidirection for the three entities, for reference type ManyTOMany and OneToMany.
     *
     * @param entity           the entity you want to create the relationship with
     * @param oldForeignEntity the entity will release the relationship with entity
     * @param newForeignEntity the entity will create new relationship with the entity
     */
    @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
    public static void updateForeignRelationshipFromEntity(Object entity, Object oldForeignEntity, Object newForeignEntity) {
        LOGGER.debug(buildLogMessage("Update the entity relationship from {0} to {1}", oldForeignEntity, newForeignEntity));
        //remove relationship at bidirection.
        removeForeignRelationshipFromEntity(entity, oldForeignEntity);
        //add relationship at bidirection.
        addForeignRelationshipToEntity(entity, newForeignEntity);
    }

    /**
     * Use to add te Entity relationship, it will create the relationship with the foreignEntity,
     * this method will make sure make the operation in bidirection, for reference type ManyTOMany and OneToMany.
     *
     * @param entity        the entity you want to create the relationship
     * @param foreignEntity the entity will create new relationship with the entity
     */
    public static void addForeignRelationshipToEntity(Object entity, Object foreignEntity) {
        LOGGER.debug(buildLogMessage("Add a entity:{0} relationship with {1}", entity, foreignEntity));
        //add at one side
        addForeignRelationshipToEntityInOneSide(entity, foreignEntity);
        //add reverse
        addForeignRelationshipToEntityInOneSide(foreignEntity, entity);
    }

    /**
     * Use to remove te Entity relationship, it will remove the relationship with the foreignEntity,
     * this method will make sure make the operation in bidirection, for reference type ManyTOMany and OneToMany.
     *
     * @param entity        the entity you want to remove the relationship
     * @param foreignEntity the entity will remove relationship with the entity
     */
    @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
    public static void removeForeignRelationshipFromEntity(Object entity, Object foreignEntity) {
        LOGGER.debug(buildLogMessage("Remove a relationship from entity:{0} at {1}", entity, foreignEntity));
        //remove at one side
        removeForeignRelationshipFromEntityInOneSide(entity, foreignEntity);
        //remove reverse
        removeForeignRelationshipFromEntityInOneSide(foreignEntity, entity);
    }

    /**
     * Use to remove the entity relationship from one side, it will remove the foreignEntity from Entity if there is a relationship
     * with them, for ManyToMany make the operation at entity without operate at the foreignEntity, if there is a OneToMany relationship
     * between the entity and the foreignEntity, it will remove the relationship on both side.
     *
     * @param entity        the focus for this operation
     * @param foreignEntity the refered one
     */
    @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
    public static void removeForeignRelationshipFromEntityInOneSide(Object entity, Object foreignEntity) {
        //make sure the entity is in Hibernate-spring context
        if (entity instanceof HibernateProxy) {
            entity = ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
        }

        List<Field> fields = getFieldFromEntity(entity);

        for (Field field : fields) { //for all the field in the class
            // field.getGenericType().toString() like List<Package.class>, and foreignEntity.getClass().getName() likes Package.Class
            if (field.getGenericType().toString().contains(foreignEntity.getClass().getName().toString())) //if it is the entity type we want
                removeEntityRelationshipOnFieldForPerticularForeignEntity(entity, field, foreignEntity);

        }

    }

    /**
     * Use to add the entity relationship from one side, it will add the foreignEntity to Entity if there is a relationship
     * with them, for ManyToMany make the operation at entity without operate at the foreignEntity, if there is a OneToMany relationship
     * between the entity and the foreignEntity, it will add the relationship on both side.
     *
     * @param entity        the focus for this operation
     * @param foreignEntity the refered one
     */
    @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
    public static void addForeignRelationshipToEntityInOneSide(Object entity, Object foreignEntity) {
        //make sure the entity is in Hibernate-spring context
        if (entity instanceof HibernateProxy) {
            entity = ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
        }

        List<Field> fields = getFieldFromEntity(entity);
        if (fields != null)
            for (Field field : fields) {
                // field.getGenericType().toString() like List<Package.class>, and foreignEntity.getClass().getName() is Package.Class
                if (field.getGenericType().toString().contains(foreignEntity.getClass().getName().toString()))//if it is the entity type we want
                    addEntityRelationshipOnFieldForPerticularForeignEntity(entity, field, foreignEntity);

            }

    }

    /**
     * Use to remove all the entity relationship from the eneity, it will remove the foreignEntity from Entity if there is a relationship
     * with them, for ManyToMany make the operation at entity without operate at the foreignEntity, if there is a OneToMany relationship
     * between the entity and the foreignEntity, it will remove the relationship on both side.
     *
     * @param entity the focus for this operation
     */
    @Transactional(readOnly = false, propagation = Propagation.SUPPORTS)
    public static void removeAllForeignRelationship(Object entity) {
        //make sure the entity is in Hibernate-spring context
        if (entity instanceof HibernateProxy) {
            entity = ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
        }

        List<Field> fields = getFieldFromEntity(entity);
        if (fields != null)
            for (Field field : fields) {
                emptyEntityRelationshipsOnField(entity, field);
            }

    }

    private static void emptyEntityRelationshipsOnField(Object entity, Field field) {
        java.lang.reflect.Type fieldType = field.getGenericType();
        if (fieldType instanceof ParameterizedType) {
            LOGGER.debug(buildLogMessage("empty Entity:{0} Relationships On Field{1}, " +
                    "entity class name:{2}, field GenericType:{3}", entity, field, entity.getClass().getSimpleName(), field.getGenericType()));

            if (isRemovableOneToManyRelationship(field)) {
                try {
                    emptyAllEntitiesOneToManyRelationship(entity, field);
                } catch (Exception e) {
                    throw new RuntimeException("Can not modify the field:" + field.getName() +
                            " value in entity: " + entity.getClass().getSimpleName(), e);
                }
            }

            if (isRemovableManyToManyRelationship(field)) {

                try {
                    emptyAllEntitiesManyToManyRelationship(entity, field);

                } catch (Exception e) {
                    throw new RuntimeException("Can not modify the field:" + field.getName() +
                            " value in entity: " + entity.getClass().getSimpleName(), e);
                }
            }
        }

    }

    private static void emptyAllEntitiesOneToManyRelationship(Object entity, Field field) throws IllegalAccessException {
        field.setAccessible(true);

        Collection<?> objs = (Collection<?>) field.get(entity);
        if (objs != null)
            for (Object obj : objs) {
                try {
                    Method method = null;
                    method = getMethodByMethodName(obj.getClass(), "set" + entity.getClass().getSimpleName(), entity.getClass(), entity.getClass());
                    LOGGER.debug(buildLogMessage("EntityRelationshipHandler.emptyAllEntitiesOneToManyRelationship , " +
                            "empty Entity:{0} Relationships On Field{1}, " +
                            "entity class name:{2}, field GenericType:{3}", obj, field, obj.getClass().getSimpleName(), field.getGenericType()));
                    method.invoke(obj, new Object[]{null});
                    Class clazz = null;
                    for (int index = 0; index < collectionType.length; index++) {
                        method = getEntityCollectionSETMethodFromForeignEntity(obj, entity, collectionType[index]);
                        if (method != null) {
                            clazz = collectionType[index];
                            break;
                        }
                    }

                    LOGGER.debug(buildLogMessage("EntityRelationshipHandler.emptyAllEntitiesOneToManyRelationship , " +
                            "call method {0}.{1}, with param NULL", entity.getClass().getSimpleName(), method.getName()));
                    method.invoke(entity, new Object[]{null});

                } catch (Exception e) {
                    throw new RuntimeException("Cannot find the proper methods in entity" + entity.getClass().getSimpleName() +
                            "for remove associations of " + obj.getClass().getSimpleName() + " for " + entity.getClass().getSimpleName() + ". Caused by : ", e);
                }
            }


    }

    private static void emptyAllEntitiesManyToManyRelationship(Object entity, Field field) throws IllegalAccessException {
        field.setAccessible(true);
        Collection<?> objs = (Collection<?>) field.get(entity);
        if (objs != null)
            for (Object obj : objs) {
                Method method = null;
                try {
                    Class clazz = null;
                    for (int index = 0; index < collectionType.length; index++) {
                        method = getEntityCollectionSETMethodFromForeignEntity(entity, obj, collectionType[index]);
                        if (method != null) {
                            clazz = collectionType[index];
                            break;
                        }
                    }
                    LOGGER.debug(buildLogMessage("EntityRelationshipHandler.emptyAllEntitiesManyToManyRelationship , " +
                            "empty Entity:{0} Relationships On Field{1}, " +
                            "entity class name:{2}, field GenericType:{3}", obj, field, obj.getClass().getSimpleName(), field.getGenericType()));
                    LOGGER.debug(buildLogMessage("EntityRelationshipHandler.emptyAllEntitiesManyToManyRelationship , " +
                            "call method {0}.{1}, with param NULL", obj.getClass().getSimpleName(), method.getName()));
                    method.invoke(obj, new Object[]{null});

                } catch (Exception e) {
                    throw new RuntimeException("Cannot find the proper methods in entity" + entity.getClass().getSimpleName() +
                            "for remove associations of " + obj.getClass().getSimpleName() + " for " + entity.getClass().getSimpleName() + ". Caused by : ", e);
                }
            }
    }

    private static void removeEntityRelationshipOnFieldForPerticularForeignEntity(Object entity, Field field, Object foreignEntity) {
        java.lang.reflect.Type fieldType = field.getGenericType();
        if (fieldType instanceof ParameterizedType) {
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeEntityRelationshipOnFieldForPerticularForeignEntity , " +
                    "remove Entity:{0} Relationships with {1}, " +
                    "entity class name:{2}, field GenericType:{3}", entity, field, entity.getClass().getSimpleName(), field.getGenericType()));
            if (isRemovableOneToManyRelationship(field)) {

                try {
                    field.setAccessible(true);
                    removeOneToManyEntityRelationshipOnFieldForPerticularForeignEntity(entity, foreignEntity);

                } catch (Exception e) {
                    throw new RuntimeException("Can not modify the field:" + field.getName() +
                            " value in entity: " + foreignEntity.getClass().getSimpleName(), e);
                }
            }

            if (isRemovableManyToManyRelationship(field)) {

                try {
                    removeManyToManyEntityRelationshipOnFieldForPerticularForeignEntity(entity, foreignEntity);

                } catch (Exception e) {
                    throw new RuntimeException("Can not modify the field:" + field.getName() +
                            " value in entity: " + foreignEntity.getClass().getSimpleName(), e);
                }
            }
        }

    }

    private static void removeOneToManyEntityRelationshipOnFieldForPerticularForeignEntity(Object entity, Object foreignEntity) {
        try {
            Method method = null;
            method = getMethodByMethodName(foreignEntity.getClass(), "set" + entity.getClass().getSimpleName(), entity.getClass(), entity.getClass());
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeOneToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                    "remove Entity:{0} Relationships with {1}, " +
                    "entity class name:{2}", entity, foreignEntity, entity.getClass().getSimpleName()));

            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeOneToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                    "call method {0}.{1}, with param NULL", foreignEntity.getClass().getSimpleName(), method.getName()));
            method.invoke(foreignEntity, new Object[]{null});
            Class clazz = null;
            for (int index = 0; index < collectionType.length; index++) {
                method = getEntityCollectionGETMethodFromForeignEntity(foreignEntity, entity, collectionType[index]);
                if (method != null) {
                    clazz = collectionType[index];
                    break;
                }
            }
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeOneToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                    "call method {0}.{1}, without param.", entity.getClass().getSimpleName(), method.getName()));
            Collection list = (Collection) method.invoke(entity); //remove the entity from the foreign Entity
            if (list != null && !list.isEmpty()) {
                list.remove(foreignEntity);
                LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeOneToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                        "remove entity:{0} from entity list {1}, then the list size is: {2}.", foreignEntity.getClass(), entity.getClass().getSimpleName(), list.size()));
            }

        } catch (Exception e) {
            throw new RuntimeException("Can't remove associations of " + foreignEntity.getClass().getSimpleName() + " for " + entity.getClass().getSimpleName() + ". Caused by : ", e);
        }
    }

    private static void removeManyToManyEntityRelationshipOnFieldForPerticularForeignEntity(Object entity, Object foreignEntity) {
        Method method = null;
        try {
            Class clazz = null;
            for (int index = 0; index < collectionType.length; index++) {
                method = getEntityCollectionGETMethodFromForeignEntity(entity, foreignEntity, collectionType[index]);
                if (method != null) {
                    clazz = collectionType[index];
                    break;
                }
            }
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeManyToManyEntityRelationshipOnFieldForPerticularForeignEntity, " +
                    "call method {0}.{1}, without.", entity.getClass().getSimpleName(), method.getName()));
            Collection list = (Collection) method.invoke(foreignEntity); //remove the entity from the foreign Entity
            if (list != null && !list.isEmpty()) {
                list.remove(entity);
                LOGGER.debug(buildLogMessage("EntityRelationshipHandler.removeManyToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                        "remove entity:{0} from entity list {1}, then the list size is: {2}.", entity.getClass().getSimpleName(), foreignEntity.getClass().getSimpleName(), list.size()));
            }
        } catch (Exception e) {
            throw new RuntimeException("Cannot find the proper methods in entity" + entity.getClass().getSimpleName() +
                    "for remove associations of " + foreignEntity.getClass().getSimpleName() + " for " + entity.getClass().getSimpleName() + ". Caused by : ", e);
        }

    }

    private static void addEntityRelationshipOnFieldForPerticularForeignEntity(Object entity, Field field, Object foreignEntity) {
        java.lang.reflect.Type fieldType = field.getGenericType();
        if (fieldType instanceof ParameterizedType) {
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.addEntityRelationshipOnFieldForPerticularForeignEntity," +
                    "empty Entity:{0} Relationships On Field{1}, " +
                    "entity class name:{2}, field GenericType:{3}", entity, field, entity.getClass().getSimpleName(), field.getGenericType()));
            if (isAttachableOneToManyRelationship(field)) {

                try {
                    addOneToManyEntityRelationshipOnFieldForPerticularForeignEntity(entity, foreignEntity);
                } catch (Exception e) {
                    throw new RuntimeException("Can not modify the field:" + field.getName() +
                            " value in entity: " + entity.getClass().getSimpleName(), e);
                }
            }

            if (isAttachableManyToManyRelationship(field)) {

                try {
                    addManyToManyEntityRelationshipOnFieldForPerticularForeignEntity(entity, foreignEntity);
                } catch (Exception e) {
                    throw new RuntimeException("Can not modify the field:" + field.getName() +
                            " value in entity: " + entity.getClass().getSimpleName(), e);
                }
            }
        }

    }

    private static void addOneToManyEntityRelationshipOnFieldForPerticularForeignEntity(Object entity, Object foreignEntity) {
        try {
            Method method = null;
            method = getMethodByMethodName(foreignEntity.getClass(), "set" + entity.getClass().getSimpleName(), entity.getClass(), entity.getClass());
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.addOneToManyEntityRelationshipOnFieldForPerticularForeignEntity, " +
                    "call method {0}.{1}, with param {2}.", foreignEntity.getClass().getSimpleName(), method.getName(), entity.getClass().getSimpleName()));
            method.invoke(foreignEntity, entity);
            Class clazz = null;
            for (int index = 0; index < collectionType.length; index++) {
                method = getEntityCollectionGETMethodFromForeignEntity(foreignEntity, entity, collectionType[index]);
                if (method != null) {
                    clazz = collectionType[index];
                    break;
                }
            }
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.addOneToManyEntityRelationshipOnFieldForPerticularForeignEntity, " +
                    "call method {0}.{1}, without param.", entity.getClass().getSimpleName(), method.getName()));
            Collection list = (Collection) method.invoke(entity); //remove the entity from the foreign Entity
            if (clazz == List.class) {
                if (list == null)
                    list = new ArrayList();
            }
            if (clazz == Set.class) {
                if (list == null)
                    list = new HashSet();
            }
            if (list != null && !list.contains(foreignEntity)) {
                list.add(foreignEntity);
                LOGGER.debug(buildLogMessage("EntityRelationshipHandler.addOneToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                        "add entity:{0} to entity list {1}, then the list size is: {2}.", foreignEntity.getClass().getSimpleName(),
                        entity.getClass().getSimpleName(), list.size()));
            }

        } catch (Exception e) {
            throw new RuntimeException("Cannot find the proper methods in entity" + entity.getClass().getSimpleName() +
                    "for update associations of " + foreignEntity.getClass().getSimpleName() + " for " + entity.getClass().getSimpleName() + ". Caused by : ", e);
        }


    }

    private static void addManyToManyEntityRelationshipOnFieldForPerticularForeignEntity(Object entity, Object foreignEntity) {
        Method method = null;
        try {
            Class clazz = null;
            for (int index = 0; index < collectionType.length; index++) {
                method = getEntityCollectionGETMethodFromForeignEntity(entity, foreignEntity, collectionType[index]);
                if (method != null) {
                    clazz = collectionType[index];
                    break;
                }
            }
            LOGGER.debug(buildLogMessage("EntityRelationshipHandler.addManyToManyEntityRelationshipOnFieldForPerticularForeignEntity, " +
                    "call method {0}.{1}, with param {2}.", entity.getClass().getSimpleName(), method.getName(), foreignEntity.getClass().getSimpleName()));
            Collection list = (Collection) method.invoke(foreignEntity); //remove the entity from the foreign Entity
            if (clazz == List.class) {
                if (list == null)
                    list = new ArrayList();
            }
            if (clazz == Set.class) {
                if (list == null)
                    list = new HashSet();
            }
            if (list != null && !list.contains(entity)) {
                list.add(entity);
                LOGGER.debug(buildLogMessage("EntityRelationshipHandler.addManyToManyEntityRelationshipOnFieldForPerticularForeignEntity , " +
                        "add entity:{0} to entity list {1}, then the list size is: {2}.", entity.getClass().getSimpleName(),
                        foreignEntity.getClass().getSimpleName(), list.size()));
            }
        } catch (Exception e) {
            throw new RuntimeException("Cannot find the proper methods in entity" + entity.getClass().getSimpleName() +
                    "for update associations of " + foreignEntity.getClass().getSimpleName() + " for " + entity.getClass().getSimpleName() + ". Caused by : ", e);
        }


    }

    private static boolean isRemovableOneToManyRelationship(Field field) {
        boolean removeOneToManyAssociations = false;
        OneToMany oneToMany = field.getAnnotation(OneToMany.class);
        List<CascadeType> oneToManyCascadeTypes = new ArrayList<CascadeType>();

        if (oneToMany != null) {
            removeOneToManyAssociations = true;
            oneToManyCascadeTypes.addAll(Arrays.asList(oneToMany.cascade()));
            if (oneToManyCascadeTypes.contains(CascadeType.ALL)) {
                removeOneToManyAssociations = false;
            }
        }
        return removeOneToManyAssociations;
    }


    private static boolean isRemovableManyToManyRelationship(Field field) {
        boolean removeManyToManyAssociations = false;
        ManyToMany manyToMany = field.getAnnotation(ManyToMany.class);
        List<CascadeType> manyToManyCascadeTypes = new ArrayList<CascadeType>();
        if (manyToMany != null && !removeManyToManyAssociations) {
            removeManyToManyAssociations = true;
            manyToManyCascadeTypes.addAll(Arrays.asList(manyToMany.cascade()));
            if (manyToManyCascadeTypes.contains(CascadeType.ALL) || manyToManyCascadeTypes.contains(CascadeType.REMOVE)) {
                removeManyToManyAssociations = false;
            }
        }
        return removeManyToManyAssociations;
    }

    private static boolean isAttachableOneToManyRelationship(Field field) {
        boolean attachOneToManyAssociations = false;
        OneToMany oneToMany = field.getAnnotation(OneToMany.class);
        List<CascadeType> oneToManyCascadeTypes = new ArrayList<CascadeType>();

        if (oneToMany != null) {
            attachOneToManyAssociations = true;
            oneToManyCascadeTypes.addAll(Arrays.asList(oneToMany.cascade()));
            if (oneToManyCascadeTypes.contains(CascadeType.ALL)) {
                attachOneToManyAssociations = false;
            }
        }
        return attachOneToManyAssociations;
    }


    private static boolean isAttachableManyToManyRelationship(Field field) {
        boolean attachManyToManyAssociations = false;
        ManyToMany manyToMany = field.getAnnotation(ManyToMany.class);
        List<CascadeType> manyToManyCascadeTypes = new ArrayList<CascadeType>();
        if (manyToMany != null && !attachManyToManyAssociations) {
            attachManyToManyAssociations = true;
            manyToManyCascadeTypes.addAll(Arrays.asList(manyToMany.cascade()));
            if (manyToManyCascadeTypes.contains(CascadeType.ALL)) {
                attachManyToManyAssociations = false;
            }
        }
        return attachManyToManyAssociations;
    }

    private static Method getEntityCollectionGETMethodFromForeignEntity(Object entity, Object foreignEntity, Class collectionClass) {
        Method method = null;
        StringBuffer methodName = null;
        for (String suffix : collectionSuffix) {
            methodName = new StringBuffer();
            if (method == null) {
                try {
                    methodName.append("get").append(entity.getClass().getSimpleName()).append(suffix);
                    method = getMethodByMethodName(foreignEntity.getClass(), methodName.toString(), entity.getClass(), entity.getClass());
                    if (method != null) {
                        Class clazz = method.getReturnType();
                        if (clazz.toString().toLowerCase().contains(collectionClass.getSimpleName().toLowerCase())) {
                            return method;
                        } else {
                            method = null;
                        }
                    }
                } catch (Exception e) {
                    //can't get the method
                }
            } else {
                return method;
            }
        }
        return method;

    }

    private static Method getEntityCollectionSETMethodFromForeignEntity(Object entity, Object foreignEntity, Class collectionClass) {
        Method method = null;
        StringBuffer methodName = null;
        for (String suffix : collectionSuffix) {
            methodName = new StringBuffer();
            if (method == null) {
                try {
                    methodName.append("set").append(entity.getClass().getSimpleName()).append(suffix);
                    method = getMethodByMethodName(foreignEntity.getClass(), methodName.toString(), entity.getClass(), collectionClass);
                } catch (Exception e) {
                    //can't get the method
                }
            } else {
                return method;
            }


        }
        return method;

    }

    private static List<Field> getFieldFromEntity(Object entity) {
        ArrayList<Field> fieldList = new ArrayList<Field>();
        Class clazz = entity.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Collections.addAll(fieldList, fields);
        while (clazz.getSuperclass().toString().toLowerCase().contains(packageName)) {
            fields = clazz.getSuperclass().getDeclaredFields();
            Collections.addAll(fieldList, fields);
            clazz = clazz.getSuperclass();
        }
        return fieldList;
    }

    private static Method getMethodByMethodName(Class clazz, String methodName, Class methodType, Class<?>... parameterTypes) {
        Method method = null;
        while (method == null) {
            Class tmpMethodType = methodType;
            methodName = methodName.replace(tmpMethodType.getSimpleName(), methodType.getSimpleName());
            while (method == null) {

                if (parameterTypes != null) {
                    try {
                        method = clazz.getMethod(methodName, parameterTypes);
                    } catch (Exception e) {
                        //can't get the method
                    }
                }
                if (method == null){
                    try {
                        method = clazz.getMethod(methodName);
                    } catch (Exception e) {
                        //can't get the method
                    }
                }
                if (method == null){
                    try {
                        method = clazz.getMethod(methodName, tmpMethodType);
                    } catch (Exception e) {
                        //can't get the method
                    }
                }
                if (method == null&&!tmpMethodType.getSuperclass().toString().toLowerCase().contains(packageName)) {
                    break;
                }
                methodName = methodName.replace(tmpMethodType.getSimpleName(), tmpMethodType.getSuperclass().getSimpleName());
                tmpMethodType = tmpMethodType.getSuperclass();

            }
            clazz = clazz.getSuperclass();
            if (method == null&&!clazz.toString().toLowerCase().contains(packageName)) {
                break;
            }
        }

        return method;
    }
}
